package com.utility.rsa;

import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;

/**
 * 因JAVA默认的RSA加密没有分段加密解密，所以当keySize为1024时，要加密的明文必须小于117个字节
 * 所以需要改进采用分段加密和解密
 */
public class RSAUtility {
	/**
	 * 加解密默认编码
	 */
	private static final String CHARSETNAME = "UTF-8";
	/**
	 * 加密方法
	 */
	private static final String ALGORITHM = "RSA";
	
	/**
	 * RSA使用公钥加密
	 * @param source 待加密字符串
	 * @param publicKey 公钥
	 * @return
	 */
	public static String encrypt( String source, String publicKey){
		try{
			final byte[] sourceBytes = source.getBytes(CHARSETNAME);
			
			byte[] publicKeyByte = new Base64().decode(publicKey);
			RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(ALGORITHM).generatePublic(new X509EncodedKeySpec(publicKeyByte));
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
			
			//因为RSA明文的长度不能大于密钥的长度，密钥长度每八位bit表示一位明文字节，另外padding填充占据了11个字节
			int keySize = rsaPublicKey.getModulus().toString(2).length();
			int MAX_ENCRYPT_BLOCKSIZE = keySize / 8 - 11;
			
			final int sourceBytesLength = sourceBytes.length;
			
			//偏移量
			int offset = 0;
			//分段数
			int segment = 0; 
	        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
	        
	        byte[] cache;
	        while (sourceBytesLength > offset) {
	             if (sourceBytesLength - offset > MAX_ENCRYPT_BLOCKSIZE) {
	                 cache = cipher.doFinal(sourceBytes, offset, MAX_ENCRYPT_BLOCKSIZE);
	             } else {
	                 cache = cipher.doFinal(sourceBytes, offset, sourceBytesLength - offset);
	             }
	             outputStream.write(cache);
	             segment = segment + 1;
	             offset = MAX_ENCRYPT_BLOCKSIZE * segment;
	         }
	        outputStream.close();
			return new Base64().encodeAsString(outputStream.toByteArray());
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * RSA使用私钥加密
	 * @param source 待解密字符串
	 * @param privateKey 私钥
	 * @return
	 */
	public static String decrypt(String source, String privateKey){
		try{
			byte[] sourceByte = new Base64().decode(source.getBytes("UTF-8"));
			byte[] privateKeyByte = new Base64().decode(privateKey);  
	        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyByte));  
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
			
			//获取密钥的keySize，然后分组时每八位表示一个字节
			int keySize = rsaPrivateKey.getModulus().toString(2).length();
			int MAX_DECRYPT_BLOCKSIZE = keySize / 8;
			
			int sourceByteLength = sourceByte.length;  
			final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			
			int offSet = 0;  
		    int segment = 0;  
		    byte[] cache;
	        while (sourceByteLength - offSet > 0) { 
	            if (sourceByteLength - offSet > MAX_DECRYPT_BLOCKSIZE) {  
	                cache = cipher.doFinal(sourceByte, offSet, MAX_DECRYPT_BLOCKSIZE);  
	            } else {  
	                cache = cipher.doFinal(sourceByte, offSet, sourceByteLength - offSet);  
	            }  
	            outputStream.write(cache);  
	            segment = segment + 1;
	            offSet = segment * MAX_DECRYPT_BLOCKSIZE;  
	        }  
	        byte[] decryptedData = outputStream.toByteArray();  
	        outputStream.close();  
		        
			return new String(decryptedData,"UTF-8");
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	
	public static void main(String[] args) throws Exception {
		Map<String,String> map = RSAKeyGenerator.getKeyPair();
		System.out.println(map);
		String s = "我我我我我我我我我我我我我我我我我我abcfsadfsfdsdf我我我我我我我我我我";
		String encodeStr = encrypt(s,map.get(RSAKeyGenerator.publicKey));
		System.out.println(encodeStr);
		String decodeStr = decrypt(encodeStr, map.get(RSAKeyGenerator.privateKey));
		System.out.println(decodeStr);
	}
}
